﻿using System;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace VMExplorer.Controls
{
  partial class PageDump : UserControl
  {
    private VMEDocument _doc = null;
    private bool _emptyPage = true;
    private byte[] _buffer = new byte[4096];
    private MemoryAddress _pageAddress = MemoryAddress.Invalid;
    private int _charWidth = 1;
    private int _bytesPerRow = 16;
    private bool _showPageOffset = true;

    public PageDump()
    {
      InitializeComponent();

      using (Graphics g = _dumpHexa.CreateGraphics())
      {
        Size size = g.MeasureString("oooo | hh hh hh hh | aaaa", _dumpHexa.Font).ToSize();
        _charWidth = size.Width / 25;
      }
    }

    public VMEDocument Document
    {
      get { return _doc; }
      set
      {
        if (_doc != null)
        {
          _doc.SelectedAddressChanged -= OnSelectedAddressChanged;
          _doc.DataUpdated -= _doc_DataUpdated;
        }
        _doc = value;
        if (_doc != null)
        {
          _doc.SelectedAddressChanged += OnSelectedAddressChanged;
          _doc.DataUpdated += _doc_DataUpdated;
        }
        UpdatePageDump();
      }
    }

    void _doc_DataUpdated (object sender, EventArgs e)
    {
      Invoke((MethodInvoker)delegate() { 
        UpdatePageDump ();
      });
    }

    void OnSelectedAddressChanged(object sender, SelectedAddressChangedEventArgs e)
    {
      UpdatePageDump();
    }

    private static bool CharIsPrintable(char c)
    {
      UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c);
      if (((unicodeCategory == UnicodeCategory.Control) && (unicodeCategory != UnicodeCategory.Format)) && ((unicodeCategory != UnicodeCategory.LineSeparator) && (unicodeCategory != UnicodeCategory.ParagraphSeparator)))
      {
        return (unicodeCategory == UnicodeCategory.OtherNotAssigned);
      }
      return true;
    }



    private void ReadPage()
    {
      _emptyPage = true;

      // Check fo invalid adress
      if (_doc == null || _doc.SelectedAddress == MemoryAddress.Invalid)
      {
        return;
      }

      // read memory into byte buffer
      IntPtr lpBuffer = Marshal.AllocHGlobal(4096);
      if (lpBuffer == IntPtr.Zero) return;

      try
      {
        _emptyPage = !_doc.ReadMemory (_pageAddress, lpBuffer, 4096);
        if (!_emptyPage)
        {
          unsafe
          {
            UnmanagedMemoryStream ums = new UnmanagedMemoryStream ((byte*)lpBuffer.ToPointer (), 4096, 4096, FileAccess.Read);
            ums.Read (_buffer, 0, 4096);
          }
        }
      }
      catch
      {
        _emptyPage = true;
      }
      finally
      {
        Marshal.FreeHGlobal (lpBuffer);
      }
    }

    private void ComputeBytePerRow()
    {
      _bytesPerRow = ((_dumpHexa.ClientSize.Width/_charWidth-1) - (_showPageOffset ? 9: 13)) / 4;
    }

    private void _dumpHexa_SizeChanged(object sender, EventArgs e)
    {
      ComputeBytePerRow();
      FormatDump();
    }

    private void FormatDump()
    {
      if (_emptyPage)
      {
        _dumpHexa.Clear(); // = "Unable to read memory";
        return;
      }

      // Dump memory into 3 parts : page offset, Hexa dump, ASCII dump
      //
      // oooo | hh hh hh hh ... hh hh | aaaa ... aa   --   9 + 4n
      // oooooooo | hh hh hh hh ... hh hh | aaaa ... aa   --   13 + 4n
      //
      StringBuilder sb = new StringBuilder(4 * 4096);
      StringBuilder asc = new StringBuilder(_bytesPerRow);
      for (int i = 0; i < 4096; i++)
      {
        if (i % _bytesPerRow == 0)
        {
          if (_showPageOffset)
          {
            sb.AppendFormat("{0:X4} | ", i);
          }
          else
          {
            sb.AppendFormat("{0} | ", (_pageAddress + i).ToString());
          }
        }

        sb.AppendFormat("{0:X2} ", _buffer[i]);

        char c = Convert.ToChar(_buffer[i]);

        if (CharIsPrintable(c))
        {
          asc.Append(c);
        }
        else
        {
          asc.Append('.');
        }

        if (i % _bytesPerRow == _bytesPerRow-1)
        {
          sb.Append("| ");
          sb.Append(asc.ToString());
          sb.AppendLine();
          asc = new StringBuilder(_bytesPerRow);
        }
      }
      _dumpHexa.Text = sb.ToString();
    }

    private void UpdatePageDump()
    {
      // Check fo invalid adress
      if (_doc==null || _doc.SelectedAddress == MemoryAddress.Invalid)
      {
        _dumpHexa.Text = string.Empty;
        return;
      }

      _pageAddress = _doc.SelectedAddress.LowerPageAligned;

      ReadPage();

      FormatDump();

      int pageOffset = _doc.SelectedAddress.LowerPageOffset;
      int row = (pageOffset / _bytesPerRow);
      int col = (pageOffset % _bytesPerRow);
      int startOffset = (_showPageOffset ? 5 : 9);
      int rowSize = startOffset + 2/*ColumnSep*/ + _bytesPerRow * 4 + 2/*ColumnSep*/ + 2/*EOL*/;
      _dumpHexa.SelectionStart = row * rowSize + (startOffset + 2/*ColumnSep*/ + col * 3);
      _dumpHexa.ScrollToCaret();
    }

    private void btnNext_Click(object sender, EventArgs e)
    {
      _doc.SelectedPage++;
    }

    private void btnPrev_Click(object sender, EventArgs e)
    {
      _doc.SelectedPage--;
    }

    private void chkOffset_CheckedChanged(object sender, EventArgs e)
    {
      _showPageOffset = ((CheckBox)sender).Checked;
      ComputeBytePerRow();
      FormatDump();
    }
  }
}
